Skip to content

perf: paginate ListEvents, gzip the HTTP responses, cache the assets - #257

Merged
jplanckeel merged 1 commit into
mainfrom
perf/payload-cache-compression
Oct 30, 2026
Merged

jplanckeel merged 1 commit into
mainfrom
perf/payload-cache-compression

Conversation

@jplanckeel

Copy link
Copy Markdown
Contributor

What

Performance pass, measured on a local base of ~8 000 events (4 changelog entries each).

Before After
Dashboard data, every 30 s events/list 10 MB (whole collection, per_page ignored) upcoming events, 16 KB gzip
JS bundle 1.7 MB, no compression, no cache header 472 KB gzip, immutable for a year
Insights, search on 30 days 1 MB 29 KB gzip
Dashboard, first load (browser) > 12 MB 737 KB
Dashboard, second visit everything again 10 KB
  • ListEvents honours per_page / page: newest first, stable order, total_count is the number of events in scope. Without per_page it still returns every event, so existing clients keep working.
  • Dashboard: the full list was only used to find the next planned event; it now searches the next 90 days.
  • gzip (klauspost/compress/gzhttp, already a dependency) on the SPA and the API, with random jitter against BREACH. The MCP endpoint (/mcp, may stream text/event-stream) is left uncompressed; checked that initialize still answers.
  • Cache: /assets/* (content-hashed by Vite) public, max-age=31536000, immutable; index.html and SPA routes no-cache.
  • Catalog list: ['catalog','list'] and ['catalogs','list'] fetched the same data twice, and the second key was not invalidated by catalog mutations (stale service lists on the event pages after editing the catalog). One shared query in lib/queries.ts.
  • staleTime 30 s by default: changing page reuses what was just fetched; mutations still invalidate immediately.

Checks

  • New TestListEventsPagination (pages, total, scope). MONGO_TEST_URI=… go test ./..., gosec, staticcheck: OK.
  • web: lint (0 issue), build:check, build:static, npm test (27): OK.
  • Browser (Playwright) against serv + web/dist: dashboard, insights, no JS error.

Not in this PR

  • Route-based code splitting of the bundle (follow-up PR).
  • Font Awesome is loaded twice: the SVG packages in the bundle and the full CSS + 155 KB webfont from cdnjs.
  • Changelog in search results (49 % of the payload): needs the detail modal to fetch it, API decision.

🤖 Generated with Claude Code

- ListEvents honours per_page and page (newest first, total in
  total_count); without per_page it still returns every event
- The dashboard looks for the next event in the next 90 days instead of
  downloading the whole collection every 30 seconds
- gzip on the SPA and the API (random jitter against BREACH), the MCP
  endpoint is left uncompressed
- Hashed /assets/ are cached for a year, index.html is revalidated
- One cache key for the catalog list: ['catalogs','list'] fetched the
  same data a second time and was not invalidated by catalog mutations
- Default staleTime of 30s: changing page no longer refetches what was
  just loaded

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot added the perf label Oct 30, 2026
@jplanckeel
jplanckeel merged commit a39d22a into main Oct 30, 2026
6 checks passed
@jplanckeel
jplanckeel deleted the perf/payload-cache-compression branch October 30, 2026 13:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant